﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.IO;

namespace GameNetworking
{
    public class StatusChangedEventArgs : EventArgs
    {


        // The argument we're interested in is a message describing the event
        private string EventMsg;

        // Property for retrieving and setting the event message
        public string EventMessage
        {
            get{return EventMsg;}
            set{EventMsg = value;}
        }

        // Constructor for setting the event message
        public StatusChangedEventArgs(string strEventMsg)
        {
            EventMsg = strEventMsg;
        }
    }

    public delegate void StatusChangedEventHandler(object sender, StatusChangedEventArgs e);

    public class Server
    {

        #region Member Variables
        private static int iUnknownCount = 1;
        private TcpClient tcpClient;
        private IPAddress ipAddress;
        bool IsServerRunning;
        public static List<UserMessage> clientMessages;
        public static List<User> clientList;
        
        public static event StatusChangedEventHandler StatusChanged;
        private static StatusChangedEventArgs e;

        private Thread thrListener;

        private TcpListener tlsClient;

        #endregion

        #region Properties
        public List<User> ClientList
        {
            get { return clientList; }
            set { clientList = value; }
        }

        public List<UserMessage> ClientMessages
        {
            get { return clientMessages; }
            set { clientMessages = value; }
        } 
        #endregion
        
        #region Constructors
        public Server(IPAddress address) 
        {
            ipAddress = address;
            clientList = new List<User>();
            clientMessages = new List<UserMessage>(); 
        }
        #endregion

        #region Methods

        // Send administrative messages
        public static void SendAdminMessage(string Message)
        {
            StreamWriter swSenderSender;

            // First of all, show in our application who says what
            e = new StatusChangedEventArgs("Administrator: " + Message);
            OnStatusChanged(e);
            foreach(User user in clientList)
            {
                // Try sending a message to each
                try
                {
                    // If the message is blank or the connection is null, break out
                    if (Message.Trim() == "" || user.Server == null)
                    {                    
                        // Send the message to the current user in the loop
                        swSenderSender = new StreamWriter(user.Server.GetStream());
                        swSenderSender.WriteLine("Administrator: " + Message);
                        swSenderSender.Flush();
                        swSenderSender = null;
                    }
                }
                catch // If there was a problem, the user is not there anymore, remove him
                {
                    RemoveUser(user);
                }
            };
       
        }

        // Send messages from one user to all the others
        public static void SendMessage(User user, string Message)
        {
            StreamWriter swSenderSender;

            // First of all, show in our application who says what
            e = new StatusChangedEventArgs(user.UserName + " says: " + Message);
            OnStatusChanged(e);

            foreach(User lUser in clientList)
                {
                    // Try sending a message to each
                    try
                    {
                        // If the message is blank or the connection is null, break out
                        if (!String.IsNullOrEmpty(Message.Trim())  || user != null)
                        {
                            // Send the message to the current user in the loop
                            swSenderSender = new StreamWriter(lUser.Server.GetStream());
                            swSenderSender.WriteLine(lUser.UserName + " says: " + Message);
                            swSenderSender.Flush();
                            swSenderSender = null;
                        }
                    }
                    catch // If there was a problem, the user is not there anymore, remove him
                    {
                        RemoveUser(lUser);
                    }
                };
        }

        // Add the user to the hash tables
        public static void AddUser(User user)
        {
            if (String.IsNullOrEmpty(user.UserName))
                AssignUserName(ref user);
            if (!clientList.Contains(user))
            {
                // First add the username and associated connection to both hash tables
                Server.clientList.Add(user);

                // Tell of the new connection to all other users and to the server form
                SendAdminMessage(user.UserName + " has joined us");
            }
        }

        public static void AssignUserName(ref User user)
        {
            user.UserName = "Uknown" + iUnknownCount;
            if (iUnknownCount == Int32.MaxValue - 1)
                iUnknownCount = 0;
            else
                iUnknownCount++;
        }

        // Remove the user from the hash tables
        public static void RemoveUser(User tcpUser)
        {
            try
            {
                if (!String.IsNullOrEmpty(tcpUser.UserName))
                {
                    // If the user is there
                    foreach (User user in clientList)
                    {
                        if (user.Server == tcpUser.Server)
                        {
                            // First show the information and tell the other users about the disconnection
                            SendAdminMessage(tcpUser.UserName + " has left the server");
                            // Remove the user from the hash table
                            tcpUser.Disconnect();
                            tcpUser = null;
                        }
                    };
                }
            }
            catch (Exception)
            {
                clientList.Remove(tcpUser);
            }
        }

        public void StartListening()
        {

            // Get the IP of the first network device, however this can prove unreliable on certain configurations
            IPAddress ipaLocal = ipAddress;

            // Create the TCP listener object using the IP of the server and the specified port
            tlsClient = new TcpListener(7777);

            // Start the TCP listener and listen for connections
            tlsClient.Start();

            // The while loop will check for true in this before checking for connections
            IsServerRunning = true;

            // Start the new tread that hosts the listener
            thrListener = new Thread(KeepListening);
            thrListener.Start();
        }

        private void KeepListening()
        {
            // While the server is running
            while (IsServerRunning == true)
            {
                // Accept a pending connection
                tcpClient = tlsClient.AcceptTcpClient();
                // Create a new instance of Connection
                Connection newConnection = new Connection(tcpClient);
            }
        }

   
        #endregion

        #region Events

        // This is called when we want to raise the StatusChanged event
        public static void OnStatusChanged(StatusChangedEventArgs e)
        {
            StatusChangedEventHandler statusHandler = StatusChanged;
            if (statusHandler != null)
            {
                // Invoke the delegate
                statusHandler(null, e);
            }
        }

        #endregion

    }
    // This class handels connections; there will be as many instances of it as there will be connected users
    class Connection
    {
        User usrUser;
        // The thread that will send information to the user
        private Thread thrSender;
        private StreamReader srReceiver;
        private StreamWriter swSender;
        private string strResponse;

        // The constructor of the class takes in a TCP connection
        public Connection(TcpClient tcpCon)
        {
            usrUser = new User(tcpCon);
            // The thread that accepts the user and awaits messages
            thrSender = new Thread(AcceptClient);
            // The thread calls the AcceptClient() method
            thrSender.Start();
        }

        private void CloseConnection()
        {
            // Close the currently open objects
            usrUser.Server.Close();
            srReceiver.Close();
            swSender.Close();
        }

        // Occures when a new user is accepted
        private void AcceptClient()
        {
            srReceiver = new System.IO.StreamReader(usrUser.Server.GetStream());
            swSender = new System.IO.StreamWriter(usrUser.Server.GetStream());

            // Read the account information from the user
            usrUser.UserName = srReceiver.ReadLine();

            // We got a response from the user
            if (usrUser.UserName != null)
            {
                if (usrUser.UserName == "Unknown")
                    Server.AssignUserName(ref usrUser);

                if (Server.clientList.Count > 0)
                {
                    foreach (User user in Server.clientList)
                    {
                        if (user.UserName == usrUser.UserName)
                        {
                            // 0 means not connected
                            swSender.WriteLine("0|This username already exists.");
                            swSender.Flush();
                            CloseConnection();
                            return;
                        }
                    };
                    // Their name means they connected successfully
                    swSender.WriteLine(usrUser.UserName);
                    swSender.Flush();

                    // Add the user to the hash tables and start listening for messages from him
                    Server.AddUser(usrUser);

                }
                else if (usrUser.UserName == "Administrator")
                {
                    // 0 means not connected
                    swSender.WriteLine("0|This username is reserved.");
                    swSender.Flush();
                    CloseConnection();
                    return;
                }
                else
                {
                    // Their name means they connected successfully
                    swSender.WriteLine(usrUser.UserName);
                    swSender.Flush();

                    // Add the user to the hash tables and start listening for messages from him
                    Server.AddUser(usrUser);
                }
            }
            else
            {
                CloseConnection();
                return;
            }

            try
            {
                // Keep waiting for a message from the user
                while ((strResponse = srReceiver.ReadLine()) != "")
                {
                    // If it's invalid, remove the user
                    if (strResponse == null)
                    {
                        Server.RemoveUser(usrUser);
                    }
                    else
                    {
                        // Otherwise send the message to all the other users
                        Server.SendMessage(usrUser, strResponse);
                    }
                }
            }
            catch
            {
                // If anything went wrong with this user, disconnect him
                Server.RemoveUser(usrUser);
            }
        }
    }
}
